package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @Author LEO
 * @Date 2021年12月14日 22:24
 */
@Service
public class SearchServiceImpl implements SearchService  {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ProductFeignClient productFeignClient;


    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 上架商品列表
     * @param skuId
     */
    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();

        // 优化异步编排

        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setId(skuInfo.getId());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setTitle(skuInfo.getSkuName());
            // goods->private Double price;
            goods.setPrice(skuInfo.getPrice().doubleValue());
            goods.setCreateTime(new Date());
            return skuInfo;
        }, threadPoolExecutor);

        CompletableFuture<Void> trademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //赋值品牌数据
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(skuInfo.getTmId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        }, threadPoolExecutor);

        CompletableFuture<Void> categoryViewCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {

            //赋值分类数据
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
        }, threadPoolExecutor);


        CompletableFuture<Void> searchAttrListCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //赋值平台属性集
            // // 平台属性集合对象
            //    // Nested 支持嵌套查询
            //    @Field(type = FieldType.Nested)
            //    private List<SearchAttr> attrs;
            List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
            List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        }, threadPoolExecutor);

       CompletableFuture.allOf(
               skuInfoCompletableFuture,
               trademarkCompletableFuture,
               categoryViewCompletableFuture,
               searchAttrListCompletableFuture
               ).join();
       // 保存goods
        this.goodsRepository.save(goods);
    }

    /**
     * 下架商品列表
     * @param skuId
     */
    @Override
    public void lowerGoods(Long skuId) {
        //根据id删除
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        // 借助redis 使用redis 必须先考虑 数据类型 Zset ZINCRBY salary 2000 tom #tom 加薪    key 起名  缓存常见的三种问题
        String hotScoreKey="hitScore";  //key=hotScore  score 递增的数据 skuId
        // 第三个参数：步长 1 表示每次加1
        Double count = redisTemplate.opsForZSet().incrementScore(hotScoreKey, "skuid:" + skuId, 1);

        // es 中  hotScore从缓存累加结果
        if (count%10==0){
            // 更新一次es
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            //保存
            this.goodsRepository.save(goods);
        }

    }

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 检索数据
     * @param searchParam
     * @return
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        /**
         * 1.根据用户的检索条件生成对应的DSL语句{方法}
         * 2.执行DSL语句并获取到结果{如何操作es,引入客户端}
         * 3.将查询出来结果进行封装{方法}
         *
         * */

        //1. 构建dsl语句
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);
        // 2.调用search 查询数据得到结果集
        SearchResponse searchResponse = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //3.将查询出来结果进行封装
         //total 总记录数在parseSearchResult 去赋值
        SearchResponseVo responseVo=  this.parseSearchResult(searchResponse);

        //默认值
        responseVo.setPageNo(searchParam.getPageNo());  // 1
        responseVo.setPageSize(searchParam.getPageSize());  //3
        // 10 3 4  9 3 3
        // 总数%每页显示条数==0？总数/每页显示条数:总数/每页显示条数+1
        //公式总结 responseVo.getTotal()总数 10   responseVo.getPageSize()每页条数3   totalPages 共显示4页
        long totalPages=(responseVo.getTotal()+responseVo.getPageSize()-1)/responseVo.getPageSize();
        responseVo.setTotalPages(totalPages); //总页数
        return responseVo;
    }


    /**
     * 生成dsl语句
     * @param searchParam
     * @return
     */
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        // 查询器GET goods/info/_search{}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //{query ----bool }
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //判断用户是否是根据分类id 进行查询  import org.springframework.util.StringUtils;
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            // query --- bool --- filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())){
            // query --- bool --- filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())){
            // query --- bool --- filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        // 有可能根据品牌id 进行过滤
        // 前端传递数据格式 ： trademark=4:小米
         String trademark= searchParam.getTrademark();
        if (!StringUtils.isEmpty(searchParam.getTrademark())){
            // 分割 4:小米 split[0] 4  split[1] 小米  坑：如果你使用的是工具类进行分割的话，不要使用spring框架的， StringUtils.split
            // query --- bool --- filter --- term
            String[] split = trademark.split(":");
            if (split!=null && split.length==2){
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }

        }

        //判断用户是否通过平台属性进行过滤
        // props=23:8G:运行内存33&props=107:华为:二级手机
        String[] props = searchParam.getProps();
        if (props!=null && props.length>0){
            for (String prop : props) {
                // prop 每个平台属性值过滤的数据 prop =23:8G:运行内存
                String[] split = prop.split(":");
                if (split!=null && split.length==3){
                    //spilt[0] 属性ID   split[1] 属性值名称
                    //  // query --- bool --- filter -- (bool -- (bool--must))
                    // 创建两个boolQuery
                 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

                 //嵌套子查询
                 BoolQueryBuilder subBcolQuery = QueryBuilders.boolQuery();
                 //设置平台属性值id
                 subBcolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                 //设置平台属性值名称
                 subBcolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                 //// query --- bool --- filter -- (bool -- must -- nested      (bool--must))
                boolQuery.must(QueryBuilders.nestedQuery("attrs",subBcolQuery, ScoreMode.None));

                // 将boolQuery 放入最外层
                    boolQuery.filter(boolQuery);
                }
            }
        }


        //判断用户是否根据keyword检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            // query --- bool --- must --- match-->title
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
        }

        //{query}
        searchSourceBuilder.query(boolQueryBuilder);

        // 分页
        // 10 条数据 0,3  3,3  6,3
       int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize());  //默认3


        //排序： order=1:asc  order=2:desc
        // 1. 表示按照哪个字段进行排序   1.表示热度排序  2.表示价格排序
        // acs 升序  desc 降序
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //声明一个字段
            String filed="";
            //分割 1:asc    2:desc
            String[] split = order.split(":");
            if (split != null && split.length==2){
                //判断按照哪个字段进行排序
                switch (split[0]){
                    case "1":
                        filed="hotScore";
                        break;
                    case "2":
                        filed="price";
                        break;
                }
                // 如果传递过来的是asc ,那么就是升序 ，如果desc 降序
                searchSourceBuilder.sort(filed, "asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
            }else {
                // 不进if语句 默认值
                searchSourceBuilder.sort("hotScore", SortOrder.DESC);
            }

        }


        //高亮查询
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");

        searchSourceBuilder.highlighter(highlightBuilder);


        // 聚合 难点
        // aggs  -- tmIdAgg   tmNameAgg、tmLogoUrlAgg同级 在tmIdAgg下
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("tmIdAgg").field("tmId")
                                            .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                                            .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"));
        //品牌聚合
        searchSourceBuilder.aggregation(aggregationBuilder);

        //销售属性聚合
        // aggs  -- attrAgg -- nested      attrNameAgg、attrValueAgg 同级  在attrIdAgg下    attrIdAgg在attrAgg下
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                                         .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                                                 .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                                                 .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                                                         )
                                       );

        // searchSourceBuilder 构建完成
        // 设置哪些字段显示，哪些字段不显示   显示 {"id","title","defaultImg","price"}  其他显示null
        searchSourceBuilder.fetchSource(new String[]{"id","title","defaultImg","price"},null);
        //GET goods/info/_search
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        searchRequest.types("info");
        // dsl 语句在哪
        String dsl = searchSourceBuilder.toString();
        System.out.println("DSL:"+dsl);
        return searchRequest;
    }


    /**
     * 查询数据结果集的转换
     * @param searchResponse
     */
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();

        SearchHits hits = searchResponse.getHits();

         //获取内层hits
        SearchHit[] subHits = hits.getHits();
        ArrayList<Goods> goodList = new ArrayList<>();
        //赋值goodList
        if (subHits!=null && subHits.length>0){
            // 循环遍历
            for (SearchHit subHit : subHits) {
                // SourceAsString 这个是json 数据格式
                String sourceAsString = subHit.getSourceAsString();
                // 将其转换为java对象 对象goods名称没有高亮显示
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //如果 高亮中的数据不为空  ,应该获取高亮名称
                if (subHit.getHighlightFields() != null){

                   if (subHit.getHighlightFields().get("title") != null){
                       //   获取高亮数据  getFragments()获取文本
                       Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                       goods.setTitle(title.toString());
                   }
                }
                goodList.add(goods);
            }
        }



        // 品牌数据从聚合中获取
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        // 数据类型转换，获取到桶集合
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();

            // 获取品牌id
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));
            // 获取品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmIdAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //获取品牌url
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);


            return searchResponseTmVo;
        }).collect(Collectors.toList());


        // 获取平台属性集合 ： 聚合 attrAgg -- nested
        ParsedNested attrAgg= (ParsedNested) aggregationMap.get("attrAgg");
        // 数据类型转换，获取到桶集合
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //attrId
            Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            searchResponseAttrVo.setAttrId(keyAsNumber.longValue());

            //attrName  attrNameAgg.getBuckets().get(0).getKeyAsString() =="key" 价格
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            searchResponseAttrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());

            //返回属性值集合 attrValueList
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            // 流式编程  Terms.Bucket::getKeyAsString 表示通过key获取里面的数据
            List<String> collect = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());

            searchResponseAttrVo.setAttrValueList(collect);
            return searchResponseAttrVo;

        }).collect(Collectors.toList());

        //赋值goodList
        searchResponseVo.setGoodsList(goodList);

        //获取总条数
        searchResponseVo.setTotal(hits.getTotalHits());

        //设置品牌数据
        searchResponseVo.setTrademarkList(trademarkList);

        //设置平台属性集合数据
        searchResponseVo.setAttrsList(attrList);



        return searchResponseVo;
    }

}
